#!/bin/bash
#/*
# * Copyright 2017 ~ 2025 the original author or authors. 
# *
# * Licensed under the Apache License, Version 2.0 (the "License");
# * you may not use this file except in compliance with the License.
# * You may obtain a copy of the License at
# *
# *      http://www.apache.org/licenses/LICENSE-2.0
# *
# * Unless required by applicable law or agreed to in writing, software
# * distributed under the License is distributed on an "AS IS" BASIS,
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# * See the License for the specific language governing permissions and
# * limitations under the License.
# * 
# * @see: https://blogs.wl4g.com/archives/60
# * @see: https://blogs.wl4g.com/archives/1825
# */

# Load environment.
. /etc/profile

# Global definition.
redisHome=${REDIS_HOME:-/usr/lib/redis-current}
redisBinDir=${REDIS_BIN_DIR:-/usr/lib/redis-current/bin}
redisConfDir=${REDIS_CONF_DIR:-/usr/lib/redis-current/conf}
redisRunDir=${REDIS_RUN_DIR:-/usr/lib/redis-current/run}
redisNodeDir=${REDIS_NODE_DIR:-/usr/lib/redis-current/node}
redisDataDir=${REDIS_DATA_DIR:-/mnt/disk1/redis/rdb}
redisLogDir=${REDIS_LOG_DIR:-/mnt/disk1/log/redis}
redisUser=${REDIS_USER:-redis}
redisGroup=${REDIS_GROUP:-redis}
cliVersion="v2.1.1-20220707"
cliLogfile="$redisLogDir/redisctl.log"

# Check environment.
#[ -z "$REDIS_HOME" ] && echo "Please check set REDIS_HOME environment." && exit -1
#[ -z "$REDIS_BIN_DIR" ] && echo "Please check set REDIS_BIN_DIR environment." && exit -1
#[ -z "$REDIS_CONF_DIR" ] && echo "Please check set REDIS_CONF_DIR environment." && exit -1
#[ -z "$REDIS_RUN_DIR" ] && echo "Please check set REDIS_RUN_DIR environment." && exit -1
#[ -z "$REDIS_NODE_DIR" ] && echo "Please check set REDIS_NODE_DIR environment." && exit -1
#[ -z "$REDIS_DATA_DIR" ] && echo "Please check set REDIS_DATA_DIR environment." && exit -1
#[ -z "$REDIS_LOG_DIR" ] && echo "Please check set REDIS_LOG_DIR environment." && exit -1

mkdir -p $redisRunDir
mkdir -p $redisNodeDir
mkdir -p $redisLogDir
mkdir -p $redisDataDir

# Input args.
CMD1=$1
CMD2=$2
CMD3=$3
CMD4=$4
CMD_ALL=$@

# Start redis cluster nodes process.
function doStart() {
  checkPermission

  local _pids=$(getPids)
  if [ -z "$_pids" ]; then
    # Check and fix AOF.
    if [ "$CMD2" == "--aof-fix" ]; then
      doCheckfixAOF
    fi

    for conf in $redisConfDir/*
    do
      logDebug "starting redis-server for $conf"
      EXEC_CMD="$redisBinDir/redis-server $conf"
      $EXEC_CMD
    done;

    log "Redis starting ..."
    while true
    do
      _pids=$(getPids)
      if [ "$_pids" == "" ]; then
        echo -n ".";
        sleep 0.8;
      else
        break;
      fi
    done
    log "INFO " "Started on "$_pids
  else
    log "INFO " "Server is running "$_pids
  fi
}

# Stop redis cluster nodes process.
function doStop() {
  local _pids=$(getPids) # Get current process code.
  if [ -z "$_pids" ]; then
    log "No running redis servers !"
  else
    log "Stopping redis $_pids .."
    kill -s TERM $_pids
    while true
    do
      _pids=$(getPids)
      if [ "$_pids" == "" ]; then
        break;
      else
        echo -n ".";
        sleep 0.8;
      fi
    done
    log "Stopped successfully."
  fi
}

# Cleanup redis cluster meta-data info.
function doCleanup() {
  checkPermission

  if [[ "$CMD2" != "--force" && -n "$(getPids)" ]]; then
      logWarn "Redis servers are running and cannot be cleanup. You must first exec '$(basename $0) stop' or use '$(basename $0) cleanup --force' to force cleaning. Note that this cleaning behavior is physical deletion and cannot be recovered. It is recommended to make a backup in advance !"
    exit -1
  fi

  log "Cleaning remove $redisNodeDir/*"
  rm -rf $redisNodeDir/*
  log "Cleaning remove $redisRunDir/*"
  rm -rf $redisRunDir/*

  if [ "$CMD2" == "--force" ]; then
    log "Cleaning remove $redisLogDir/*"
    rm -rf $redisLogDir/*
    log "Cleaning remove $redisDataDir/*"
    rm -rf $redisDataDir/*
  fi
}

# Initializing create redis cluster.
function doInitCluster() {
  checkPermission

  local _redisPasswd=$CMD2
  local _redisNodes=$(echo $CMD3|sed 's/,/ /g')
  if [ -z "$_redisPasswd" ]; then
    log "cluster <password> <[host1:port1],[host2:port2] ...>"
    exit 0
  elif [ -z "$_redisNodes" ]; then
    log "cluster <password> <[host1:port1],[host2:port2] ...>"
    exit 0
  fi

  EXEC_CMD=exec /bin/echo yes|$redisBinDir/redis-cli -a $_redisPasswd --cluster create --cluster-replicas 1 $_redisNodes
  $EXEC_CMD
}

# Repair redis cluster down to up.(e.g. cluster is shutdown)
function doRepairCluster() {
  checkPermission

  local _redisPasswd=$CMD2
  local _redisNode=$CMD3
  if [ -z "$_redisPasswd" ]; then
    log "Cannot repair cluster, redis password must is not empty !"
    exit 0
  elif [ -z "$_redisNode" ]; then
    log "Cannot repair cluster, redis nodes must is not empty !"
    exit 0
  fi

  EXEC_CMD="$redisBinDir/redis-cli -a $_redisPasswd --cluster fix $_redisNode"
  $EXEC_CMD
}

# Print redis cluster process info.
function doStatus() {
  local _pids=$(getPids)
  if [ -z "$_pids" ]; then
    log "No running information!"
  else
    log "--- Redis Processes Summary ---\n"
    #local localIp=$(ifconfig|grep -A 4 -E '^eno*|^enp*|^ens*|^eth*|^wlp*'|grep 'inet'|awk '{print $2}'|head -1 2>/dev/null)
    local localIp=$(ip a|grep -E '^[0-9]+: em*|^[0-9]+: eno*|^[0-9]+: enp*|^[0-9]+: ens*|^[0-9]+: eth*|^[0-9]+: wlp*' -A2|grep inet|awk -F ' ' '{print $2}'|cut -f1 -d/|xargs echo|sed 's# #,#g')
    ps ax | grep -i $redisHome | grep -v grep | awk '{print "node/'$localIp' "$0}'
  fi
}

# Update redis cluster password.
function doPassword() {
  local _redisPasswd=$CMD2
  local _newRedisPasswd=$CMD3
  local _redisNodes=(${CMD4//,/ })

  if [ -z "$_redisPasswd" ]; then
    log "password <oldPassword> <newPassword> <[host:port1],[host:port2] ...>"
    exit 0
  elif [ -z "$_newRedisPasswd" ]; then
    log "password <oldPassword> <newPassword> <[host:port1],[host:port2] ...>"
    exit 0
  elif [ -z "$_redisNodes" ]; then
    log "password <oldPassword> <newPassword> <[host:port1],[host:port2] ...>"
    exit 0
  fi

  for node in ${_redisNodes[@]}
  do
    IFS="\:" # Set shell string separators.
    hostAndPort=($node)
    host=${hostAndPort[0]}
    port=${hostAndPort[1]}
    EXEC_CMD="${redisHome}/bin/redis-cli -h ${host} -p ${port} -a ${_redisPasswd} config set masterauth ${_newRedisPasswd}"
    /bin/bash -c $EXEC_CMD
    EXEC_CMD="${redisHome}/bin/redis-cli -h ${host} -p ${port} -a ${_redisPasswd} config set requirepass ${_newRedisPasswd}"
    /bin/bash -c $EXEC_CMD
  done
}

# Check and fix appendonly files.
function doCheckfixAOF() {
  log "Checking fix aof files ..."
  cd $redisDataDir
  for f in $(ls *|grep appendonly); do
    $redisHome/bin/redis-check-aof "$f" >/dev/null 2>&1
    if [ $? -ne 0 ]; then
      echo Y | $redisHome/bin/redis-check-aof --fix "$f"
    fi
  done
}

# Get redis nodes process ID.
function getPids() {
  local _pids=$(ps ax | grep -i $redisHome | grep -v grep | awk '{print $1}')
  echo $_pids # Result value.
  return 0 # Return execution code.
}

# Core logging.
# e.g1: log "error" "Failed to xxx"
# e.g2: log "xxx complete!"
function log() {
  local logLevel="\033[33mINFO\033[0m "
  local logContent=$1
  if [[ $# > 1 ]]; then
    logLevel=$1
    logContent=$2
  fi
  local logMsg="[$logLevel] $(date '+%Y-%m-%d %H:%M:%S') - $logContent"
  echo -e "$logMsg"
  echo -e "$logMsg" >> ${cliLogfile}
}

# Debug logging.
function logDebug() {
  log "\033[37mDEBUG\033[0m" "$@"
}

# Warn logging.
function logWarn() {
  log "\033[33mWARN \033[0m" "$@"
}

# Error logging.
function logErr() {
  log "\033[31mERROR\033[0m" "$@"
}

function printVersion() {
  echo "Redis cluster lightweight management CLI of $cliVersion"
}

function checkPermission() {
  if [[ "$USER" != "$redisUser" ]]; then
    logErr " In order to standardize the use, please switch the user '$redisUser' to execute this command."
    exit 2
  fi
}

# --- Main. ---
case $CMD1 in
  version|-version|--version|v|-v)
    printVersion
    ;;
  status)
    doStatus
    ;;
  start)
    doStart
    ;;
  stop)
    doStop
    ;;
  restart)
    doStop
    doStart
    ;;
  cluster)
    doInitCluster
    ;;
  repair)
    doRepairCluster
    ;;
  password)
    doPassword
    ;;
  cleanup)
    doCleanup
    ;;
  *)
  echo $"
$(printVersion)

Usage: ./$(basename $0) [OPTIONS] [arg1] [arg2] ...
    start                                                          Startup all local nodes of the redis-server cluster.
          [--aof-fix]                                              Used to repair the damaged AOF data file during the last unexpected outage.
     stop                                                          Stop all local nodes of the redis-server cluster.
  restart                                                          Restart all local nodes of the redis-server cluster.
   status                                                          Print the process status of all local nodes of the redis cluster.
  cluster <password> <host1:port1,host2:port2...>                  Newly initialize a redis cluster, If it is an initialized cluster, it may fail,
                                                                   but you can destroy it first and then re initialize it.
   repair                                                          Repair the specified node of the cluster.
 password <oldPassword> <newPassword> <host1:port1,host2:port2...> Modify the password of the specified node of the redis cluster.
  cleanup                                                          Destroy data for redis cluster local nodes all.
          [--force]                                                When the cluster node is still running, it needs to be enforced to physically delete
                                                                   data files such as nodes,rdb,aof,logs.
"
  exit 2
esac
